#pragma once

#include "core.h"
#include <d3d9.h>
#include <d3dx9.h>
#include "../ObjectTypes.h"

// play with this value to affect how easily objects will
// go to sleep when they're resting on one another
#define MOTION_BIAS		0.5

namespace physics {

	class RigidBody
	{
	protected:
		bool		activated;
		real		motion;

		// mass
		real		inverseMass;
		real		mass;

		// linear
		Vector3		position;
		Vector3		velocity;
		Vector3		acceleration;
		Vector3		lastFrameAcceleration;
		real		linearDamping;	
		
		// angular
		Vector3		rotation;
		Quaternion	orientation;
		real		angularDamping;
		
		// matrices
		Matrix4		transformMatrix;
		Matrix3		inverseInertiaTensor;
		Matrix3		inverseInertiaTensorWorld;

		// Newtonian stuff
		Vector3		forceAccum;
		Vector3		torqueAccum;

		bool isAwake;
		bool canSleep;

		// adding hacks to the base class to 
		// play sounds for collisions and other 
		// events. Ideally this class would be
		// independent of other components
		ObjectMaterial   mMaterial;

	public:
		// constructor. All RigidBodys are activated unless explictly deactivated
		RigidBody();
		virtual ~RigidBody();

		// calculates the matrix information
		// of the object, based on its data
		void calculateDerivedData();

		// apply a force to this object
		void applyForce(const Vector3 &force);
		void applyTorque(const Vector3 &torque);

		// clear accumulators
		void clearAccumulators();

		// integrate for this cycle
		virtual void integrate(real dt);

		// zeroes all values in the Z-axis to prevent
		// Z-axis motion (rotation and translation)
		void zeroZAxis();
		
		// stops the rigid body in place
		void stop();

		// apply a force to a given point, both given in world space
		// can turn into a force and a torque since it doesn't apply to
		// the COM
		void applyForceAtPoint(const Vector3 &force, const Vector3 &point);

		// apply a force at a given body point, both given in world space
		// can turn into a force and a torque since it doesn't apply to
		// the COM
		void applyForceAtBodyPoint(const Vector3 &force, const Vector3 &point);

		// returns this point in world coordinates relative to the COM
		Vector3 getPointInWorldSpace(const Vector3 &point) const;

		// returns this poitn in local coordinates
		Vector3 getPointInLocalSpace(const Vector3 &point) const;

		// gets a direction in world space
		Vector3 getDirectionInWorldSpace(const Vector3 &direction) const;

		// gets a direction in local space
		Vector3 getDirectionInLocalSpace(const Vector3 &direction) const;


		// mass
		void setMass(const real mass);
		real getMass() const;
		void setInverseMass(const real inverseMass);
		real getInverseMass() const;
		bool hasFiniteMass() const;

		// inertia tensors
		void setInertiaTensor(const Matrix3 &inertiaTensor);
		void getInertiaTensor(Matrix3 *inertiaTensor) const;
		Matrix3 getInertiaTensor() const;

		void getInertiaTensorWorld(Matrix3 *inertiaTensor) const;
		Matrix3 getInertiaTensorWorld() const;
		
		void setInverseInertiaTensor(const Matrix3 &inverseInertiaTensor);
		void getInverseInertiaTensor(Matrix3 *inverseInertiaTensor) const;
		Matrix3 getInverseInertiaTensor() const;

		void getInverseInertiaTensorWorld(Matrix3 *inverseInertiaTensorWorld) const;
		Matrix3 getInverseInertiaTensorWorld() const;

		// damping
		void setDamping(const real linearDamping, const real angularDamping);

		void setLinearDamping(const real linearDamping);
		real getLinearDamping() const;

		void setAngularDamping(const real angularDamping);
		real getAngularDamping() const;

		// position
		void setPosition(const real x, const real y, const real z);
		void setPosition(const Vector3 &position);
		void getPosition(Vector3 *position) const;
		Vector3 getPosition() const;

		// velocity
		void setVelocity(const Vector3 &velocity);
		void setVelocity(const real x, const real y, const real z);
		void getVelocity(Vector3 *velocity) const;
		Vector3 getVelocity() const;
		void addVelocity(const Vector3 &deltaVelocity);

		// acceleration
		void setAcceleration(const Vector3 &acceleration);
		void setAcceleration(const real x, const real y, const real z);
		void getAcceleration(Vector3 *acceleration) const;
		Vector3 getAcceleration() const;

		// rotation
		void setRotation(const real x, const real y, const real z);
		void setRotation(const Vector3 &rotation);
		void getRotation(Vector3 *rotation) const;
		Vector3 getRotation() const;
		void addRotation(const Vector3 &deltaRotation);
		
		// awake state
		bool getAwake() const;
		void setAwake(const bool awake=true);
		bool getCanSleep() const;
		void setCanSleep(const bool canSleep = true);

		// activation (deactivated objects don't get involved in collisions
		void activate();
		void deactivate();
		bool getActivated() const;

		// orientation
		void setOrientation(const Quaternion &orientation);
		void setOrientation(const real r, const real i, const real j, const real k);
		void getOrientation(Quaternion *orientation) const;
		Quaternion getOrientation() const;
		void getOrientation(Matrix3 *matrix) const;
		void getOrientation(real matrix[9]) const;
		
		// transform matrix
		void getTransform(Matrix4 *transform) const;
		void getTransform(real matrix[16]) const;
		void getGLTransform(float matrix[16]) const;
		D3DXMATRIX getDXTransform();
		Matrix4 getTransform() const;

		// last frame acceleration
		void getLastFrameAcceleration(Vector3 *linearAcceleration) const;
		Vector3 getLastFrameAcceleration() const;

		// Get and Set the material. A quick hack so we can play sounds
		// during collisions and other events.
		ObjectMaterial GetMaterial(void) {return mMaterial;};
		void SetMaterial(ObjectMaterial material) {mMaterial = material;};
	};
}
